<!DOCTYPE html>
<html ng-app="App">
<head>
    <title>LogView | Zoraxy</title>
    <meta charset="UTF-8">
    <meta name="theme-color" content="#4b75ff">
    <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
    <link rel="icon" type="image/png" href="../favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
    <link rel="stylesheet" href="../script/semantic/semantic.min.css">
    <script type="text/javascript" src="../script/jquery-3.6.0.min.js"></script>
    <script type="text/javascript" src="../script/semantic/semantic.min.js"></script>
    <link rel="stylesheet" href="../main.css">
    <style>
        .clickable{
            cursor: pointer;
        }
        .clickable:hover{
            opacity: 0.7;
        }

        /* Panel menu */
        .logfile_menu_btn{
            height: 2.8em !important;
            margin-top: 0.4em !important;
            margin-right: 0 !important;
        }

        /* Log file list */
        .logfile{
            padding-left: 1em !important;
            position: relative;
            padding-right: 1em !important;
        }

        .loglist{
            background-color: rgb(250, 250, 250);
        }

        .logfile .showing{
            position: absolute;
            top: 0.18em;
            right: 0em;
            margin-right: -0.4em;
            opacity: 0;
        }

        .logfile.active .showing{
            opacity: 1;
        }

        #logrender{
            width: 100% !important;
            height: calc(100% - 1.2em);
            min-height: calc(90vh - 1.2em) !important;
            border: 0px solid transparent !important;
            background-color: #252630;
            color: white;
            font-family: monospace;
            overflow-x: scroll !important;
            white-space: pre;
            resize: none;
            scrollbar-width: thin;
            font-size: 1.2em;
        }

        #logrender::selection{ 
            background:#3643bb; 
            color:white; 
        }

        #errorHighlightWrapper{
            background-color: #f9f9f9;
            padding: 1em;
            border-radius: 0.3em;
            max-height:300px;
            overflow-y:auto;
            margin-bottom:1em;
            border: 1px solid #b3b3b3;
        }

        @media (min-width: 768px) {
            #logfileDropdown {
                margin-left: 0.4em !important;
            }

            .logfile_menu_btn{
                margin-left:0.4em !important;
            }

            #logfileDropdown{
                margin-left: 0.4em !important;
            }
        }

        @media (max-width: 767px) {
            #toggleFullscreenBtn {
                display: none !important;
            }

            #logrenderWrapper{
                margin-left: 0em !important;
                margin-right: 0em !important;
            }
        }
    </style>
</head>
<body>
    <!-- Currently not darktheme ready -->
    <link rel="stylesheet" href="../darktheme.css">
     <!-- <script src="../script/darktheme.js"></script> -->
    <br>
    <div class="ui container">
        <div class="ui stackable secondary menu">
            <div class="item" style="font-weight: bold;">
                Zoraxy LogView
            </div>
             <a class="item active panel_menu_btn" id="logViewMenu">
                Log View
            </a>
            <a class="item panel_menu_btn" id="summaryMenu">
                Summary
            </a>
            <!-- <a class="item panel_menu_btn" id="settingsMenu">
                Settings
            </a> -->
        </div>
    </div>
    <div class="ui container">
        <div class="ui divider"></div>
        <div class="ui stackable secondary menu">
            <!-- Filter Dropdown -->
            <div class="ui selection dropdown" id="filterDropdown" style="margin-top:0.4em;">
                <div class="text">Select Filter</div>
                <i class="dropdown icon"></i>
                <div class="menu">
                    <div class="item" data-value="all"><i class="filter icon"></i> All</div>
                    <div class="item" data-value="system"><i class="blue info circle icon"></i> System</div>
                    <div class="item" data-value="request"><i class="green exchange icon"></i> Request</div>
                    <div class="item" data-value="error"><i class="red exclamation triangle icon"></i> Error</div>
                </div>
            </div>

            <!-- Log File Dropdown -->
            <div class="ui selection dropdown" id="logfileDropdown" style="margin-top: 0.4em; height: 2.8em;">
                <div class="text">Select Log File</div>
                <i class="dropdown icon"></i>
                <div class="menu" id="logfileDropdownMenu">
                    <!-- Log files will be populated here -->
                </div>
            </div>

            <!-- Lines Dropdown -->
            <div class="ui selection dropdown" id="linesDropdown" style="margin-left: 0.4em; margin-top: 0.4em; height: 2.8em;">
                <div class="text">Last 100 Lines</div>
                <i class="dropdown icon"></i>
                <div class="menu">
                    <div class="item selected" data-value="100">Last 100 Lines</div>
                    <div class="item" data-value="300">Last 300 Lines</div>
                    <div class="item" data-value="500">Last 500 Lines</div>
                    <div class="item" data-value="1000">Last 1000 Lines</div>
                    <div class="item" data-value="2000">Last 2000 Lines</div>
                    <div class="item" data-value="all">All Lines</div>
                </div>
            </div>
            
            <!-- Download Button -->
            <button class="ui icon basic button logfile_menu_btn" id="downloadLogBtn" title="Download Current Log File">
                <i class="black download icon"></i>
            </button>

            <!-- Open in New Tab Button -->
            <button class="ui icon basic button logfile_menu_btn" onclick="openLogInNewTab();" title="Open in New Tab">
                <i class="external alternate icon"></i>
            </button>

            <!-- Refresh -->
            <button class="ui icon basic button logfile_menu_btn" id="refreshLogBtn" title="Refresh Current Log">
                <i class="black sync icon"></i>
            </button>
        </div>
    </div>
    <br><br>
    <!-- Summary View -->
    <div id="summary" class="ui container subpanel">
        <h3 class="ui header">
            Summary
        </h3>
        <div class="ui divider"></div>
        <div class="ui small statistics" style="display: flex; justify-content: center;">
            <div class="statistic">
                <div class="value" id="successRequestsCount"></div>
                <div class="label">Success Requests</div>
            </div>
            <div class="statistic">
                <div class="value" id="errorRequestsCount"></div>
                <div class="label">Error Requests</div>
            </div>
            <div class="statistic">
                <div class="value" id="totalRequestsCount"></div>
                <div class="label">Total Requests</div>
            </div>
        </div>
        <div class="ui divider"></div>
        <!-- Error Highlight Section -->
        <h4 class="ui header">Error Highlights</h4>
        <div id="errorHighlightWrapper">
            <p><i class="ui green circle check icon"></i> No error data loaded</p>
        </div>

        <div class="ui divider"></div>
        <div id="analyzer">
            <div class="ui info message">
                <div class="header">
                    No log file selected
                </div>
                <p>Please select a log file from the dropdown menu to view analysis and summary.</p>
            </div>
        </div>
    </div>

    <!-- Log Viewer -->
    <div id="logviewer" class="subpanel" style="display:none;">
        <div id="logrenderWrapper" class="ui container" style="position:relative;">
            <textarea id="logrender" spellcheck="false" readonly="true">
Pick a log file from the menu to start debugging
            </textarea>
            <!-- Full Screen Button -->
            <button id="toggleFullscreenBtn" class="ui icon black button" title="Toggle Full Screen" style="border: 1px solid white; position:absolute; top:0.5em; right:0.5em; z-index:999;">
                <i class="expand arrows alternate icon"></i>
            </button>
            <!-- Scroll to Bottom Button -->
            <button id="scrollBottomBtn" class="ui icon black button" title="Scroll to Bottom" style="border: 1px solid white; position:absolute; bottom:0.5em; right:0.5em; z-index:999;">
                <i class="angle double down icon"></i>
            </button>
            <script>
                $("#scrollBottomBtn").on("click", function() {
                    var textarea = document.getElementById('logrender');
                    textarea.scrollTop = textarea.scrollHeight;
                });
            </script>
        </div>
        <script>
            // Toggle full screen for logrenderWrapper
            $("#toggleFullscreenBtn").on("click", function() {
                $("#logrenderWrapper").toggleClass("ui container");
                $(this).find("i").toggleClass("expand arrows alternate compress arrows alternate");
            });
        </script>
        <br>
        <div class="ui container">
            <div class="ui divider"></div>
            <div class="ui toggle checkbox">
                <input type="checkbox" id="enableAutoScroll" onchange="handleAutoScrollTicker(event, this.checked);">
                <label>Auto Refresh<br>
                <small>Refresh the viewing log every 3 seconds</small></label>
            </div>
            <div class="ui divider"></div>
            <small>Notes: Some log files might be huge. Make sure you have checked the log file size before opening</small>
        </div>
    </div>

    <!-- Settings -->
    <div id="settings" class="ui container subpanel" style="display:none;">
        <h3 class="ui header">
            Settings
        </h3>
    </div> 

    <br>
    <div class="ui container">
        <div class="ui divider"></div>
        <small>Zoraxy LogView - Advance log viewer for Zoraxy log files</small>
        <br><br><br>
    </div>
</body>
<script>
    //LogView Implementation
    var currentFilter = "all";
    var currentLines = "100";
    var currentOpenedLogURL = "";
    var currentLogFile = "";
    var autoscroll = false;

    $(".checkbox").checkbox();
    $(".dropdown").dropdown();

    /* Menu Subpanel Switch */
    $(".subpanel").hide();
    $("#logviewer").show();
    $(".panel_menu_btn").on("click", function() {
        var id = $(this).attr("id");
        $(".subpanel").hide();
        $(".ui.menu .item").removeClass("active");
        $(this).addClass("active");
        if (id === "summaryMenu") {
            $("#summary").show();
        } else if (id === "logViewMenu") {
            $("#logviewer").show();
        } else if (id === "settingsMenu") {
            $("#settings").show();
        }
    });

    /* Refresh Button */
    $("#refreshLogBtn").on("click", function() {
        if (currentLogFile) {
            openLog(null, null, currentLogFile, currentFilter, currentLines);
            loadLogSummary(currentLogFile);
        } else {
            alert('Please select a log file first.');
        }
    });

    /* Log file dropdown */
    function populateLogfileDropdown() {
        $.get("/api/log/list", function(data){
            console.log(data);
            let $menu = $("#logfileDropdownMenu");
            $menu.html("");
            for (let [key, value] of Object.entries(data)) {
                value.reverse();
                value.forEach(file => {
                    $menu.append(
                        `<div class="item" data-value="${file.Filename}" data-category="${key}">${file.Title} (${formatBytes(file.Filesize)})</div>`
                    );
                });
            }
            $('#logfileDropdown').dropdown('refresh');
        });
    }
    $('#logfileDropdown').dropdown({
        onChange: function(value, text, $choice) {
            if (value) {
                openLog(null, $choice.data('category'), value, currentFilter, currentLines);
                loadLogSummary(value);
            }
        }
    });
    populateLogfileDropdown();

    /* Filter dropdown */
    $('#filterDropdown').dropdown({
        onChange: function(value) {
            currentFilter = value;
            if (!currentLogFile) {
                return;
            }
            $(".filterbtn.active").removeClass("active");
            $(`.filterbtn[filter="${value}"]`).addClass("active");
            openLog(null, null, currentLogFile, currentFilter, currentLines);
        }
    });

    // Set default filter to "error"
    $('#filterDropdown').dropdown('set selected', 'all');
    currentFilter = "all";

    /* Lines dropdown */
    $('#linesDropdown').dropdown({
        onChange: function(value) {
            currentLines = value;
            if (!currentLogFile) {
                return;
            }
            openLog(null, null, currentLogFile, currentFilter, currentLines);
        }
    });

    // Set default lines to 100
    $('#linesDropdown').dropdown('set selected', '100');
    currentLines = "100";

    /* Log download button */
    $("#downloadLogBtn").on("click", function() {
        if (!currentLogFile) {
            alert("Please select a log file first.");
            return;
        }
        // Always download the full log file, regardless of current line limit
        let downloadURL = "/api/log/read?file=" + currentLogFile + "&filter=" + currentFilter + "&lines=all";
        $.get(downloadURL, function(data) {
            if (data.error !== undefined) {
                alert(data.error);
                return;
            }
            let blob = new Blob([data], {type: "text/plain"});
            let url = URL.createObjectURL(blob);
            let a = document.createElement("a");
            a.href = url;
            a.download = currentLogFile || "log.txt";
            document.body.appendChild(a);
            a.click();
            setTimeout(function() {
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            }, 100);
        });
    });

    /* Analyzer */
    function loadLogSummary(filename){
        $.get("/api/log/summary?file=" + filename, function(data){
            if (data.error !== undefined){
                $("#analyzer").html("<div class='ui negative message'><div class='header'>Error</div><p>" + data.error + "</p></div>");
                return;
            }
            
            $("#successRequestsCount").text(data.total_valid || 0);
            $("#errorRequestsCount").text(data.total_errors || 0);
            $("#totalRequestsCount").text(data.total_requests || 0);
            renderSummaryToHTML(data);
        });

        $.get("/api/log/errors?file=" + encodeURIComponent(filename), function(data) {
            renderErrorHighlights(data);
        }).fail(function() {
            $("#errorHighlightWrapper").html(
                `<div class="ui error message">
                    <div class="header">Failed to load error highlights</div>
                </div>`
            );
        });

    }

    function renderSummaryToHTML(data){
        /* Render summary analysis to #analyzer */
        let html = `
            <div class="ui stackable grid">
                <div class="eight wide column">
                    <h4 class="ui header">Request Methods</h4>
                    <canvas id="requestMethodsChart" height="180"></canvas>
                </div>
                <div class="eight wide column">
                    <h4 class="ui header">Hits Per Day</h4>
                    <canvas id="hitsPerDayChart" height="180"></canvas>
                </div>
            </div>
            <div class="ui divider"></div>
            <div class="ui stackable grid">
                <div class="sixteen wide column">
                    <h4 class="ui header">Hits Per Site</h4>
                    <div id="siteCharts"></div>
                </div>
            </div>
            <div class="ui divider"></div>
            <div class="ui stackable grid">
                <div class="eight wide column">
                    <h4 class="ui header">Unique IPs</h4>
                    <table class="ui celled table">
                        <thead><tr><th>IP</th><th>Requests</th></tr></thead>
                        <tbody>
                            ${Object.entries(data.unique_ips)
                                .sort((a, b) => b[1] - a[1])
                                .map(([ip, count]) => `<tr><td>${ip}</td><td>${count}</td></tr>`)
                                .join("")}
                        </tbody>
                    </table>
                </div>
                <div class="eight wide column">
                    <h4 class="ui header">Top User Agents</h4>
                    <table class="ui celled table">
                        <thead><tr><th>User Agent</th><th>Requests</th></tr></thead>
                        <tbody>
                            ${Object.entries(data.top_user_agents)
                                .sort((a, b) => b[1] - a[1])
                                .map(([ua, count]) => `<tr><td style="word-break:break-all;">${ua}</td><td>${count}</td></tr>`)
                                .join("")}
                        </tbody>
                    </table>
                </div>
            </div>
            <div class="ui divider"></div>
            <div class="ui stackable grid">
                <div class="sixteen wide column">
                    <h4 class="ui header">Top Paths</h4>
                    <table class="ui celled table">
                        <thead><tr><th>Path</th><th>Requests</th></tr></thead>
                        <tbody>
                            ${Object.entries(data.top_paths)
                                .sort((a, b) => b[1] - a[1])
                                .map(([path, count]) => `<tr><td style="word-break:break-all;">${path}</td><td>${count}</td></tr>`)
                                .join("")}
                        </tbody>
                    </table>
                </div>
            </div>
        `;

        $("#analyzer").html(html);

        // Load Chart.js if not loaded
        function loadChartJs(cb) {
            if (window.Chart) { cb(); return; }
            let s = document.createElement("script");
            s.src = "https://cdn.jsdelivr.net/npm/chart.js";
            s.onload = cb;
            document.head.appendChild(s);
        }

        // Prepare data for charts
        function getDateRangeLabels(hitPerDay) {
            let dates = Object.keys(hitPerDay).sort();
            if (dates.length === 0) return [];
            let start = new Date(dates[0]);
            let end = new Date(dates[dates.length - 1]);
            let labels = [];
            for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
                let y = d.getFullYear(), m = (d.getMonth() + 1).toString().padStart(2, "0"), day = d.getDate().toString().padStart(2, "0");
                labels.push(`${y}-${m}-${day}`);
            }
            return labels;
        }

        loadChartJs(function() {
            // 1. Pie chart for request methods
            let reqMethods = data.request_methods || {};
            let reqLabels = Object.keys(reqMethods);
            let reqCounts = Object.values(reqMethods);
            new Chart(document.getElementById("requestMethodsChart"), {
                type: "pie",
                data: {
                    labels: reqLabels,
                    datasets: [{
                        data: reqCounts,
                        backgroundColor: ["#4b75ff", "#21ba45", "#db2828", "#fbbd08", "#b5cc18", "#a333c8"]
                    }]
                },
                options: { responsive: true, plugins: { legend: { position: "bottom" } } }
            });

            // 2. Histogram for hit_per_day
            let hitPerDay = data.hit_per_day || {};
            let dayLabels = getDateRangeLabels(hitPerDay);
            let dayCounts = dayLabels.map(d => hitPerDay[d] || 0);
            new Chart(document.getElementById("hitsPerDayChart"), {
                type: "bar",
                data: {
                    labels: dayLabels,
                    datasets: [{
                        label: "Hits",
                        data: dayCounts,
                        backgroundColor: "#4b75ff"
                    }]
                },
                options: {
                    responsive: true,
                    plugins: { legend: { display: false } },
                    scales: { x: { ticks: { autoSkip: false, maxRotation: 90, minRotation: 45 } } }
                }
            });

            // 3. Line chart for each site in hit_per_site
            let siteChartsDiv = $("#siteCharts");
            let siteData = data.hit_per_site || {};
            let i = 0;
            for (let [site, counts] of Object.entries(siteData)) {
                let canvasId = "siteChart_" + i;
                siteChartsDiv.append(`
                    <div style="margin-bottom:2em;">
                        <b>${site}</b>
                        <canvas id="${canvasId}" height="100"></canvas>
                    </div>
                `);
                // If counts length doesn't match dayLabels, pad left with zeros
                let paddedCounts = counts.slice(-dayLabels.length);
                while (paddedCounts.length < dayLabels.length) paddedCounts.unshift(0);
                new Chart(document.getElementById(canvasId), {
                    type: "line",
                    data: {
                        labels: dayLabels,
                        datasets: [{
                            label: "Hits",
                            data: paddedCounts,
                            fill: false,
                            borderColor: "#21ba45",
                            backgroundColor: "#21ba45",
                            tension: 0.2
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: { legend: { display: false } },
                        scales: { x: { ticks: { autoSkip: false, maxRotation: 90, minRotation: 45 } } }
                    }
                });
                i++;
            }
        });
    }


    function openLog(object, catergory, filename, filter="all", lines="100"){
        $(".logfile.active").removeClass('active');
        $(object).addClass("active");
        currentLogFile = filename;
        currentOpenedLogURL = "/api/log/read?file=" + filename + "&filter=" + filter + "&lines=" + lines;
        $.get(currentOpenedLogURL, function(data){
            if (data.error !== undefined){
                alert(data.error);
                return;
            }
            renderLogWithCurrentFilter(data);
        });
    }

    function openLogInNewTab(){
        if (currentOpenedLogURL != ""){
            window.open(currentOpenedLogURL);
        }
    }

    function formatBytes(x){
        var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        let l = 0, n = parseInt(x, 10) || 0;
        while(n >= 1024 && ++l){
            n = n/1024;
        }
        return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l]);
    }

    //Filter the log and render it to text area based on current filter choice
    function renderLogWithCurrentFilter(data){
        $("#logrender").val(data);
        var textarea = document.getElementById('logrender');
        textarea.scrollTop = textarea.scrollHeight;
    }

    /* Auto scroll function */
    setInterval(function(){
        if (autoscroll){
            //Update the log and scroll to bottom
            if (currentOpenedLogURL != ""){
                $.get(currentOpenedLogURL, function(data){
                    if (data.error !== undefined){
                        console.log(data.error);
                        return;
                    }
                    renderLogWithCurrentFilter(data);
                }); 
            }
        }
    }, 3000);
    function handleAutoScrollTicker(event, checked){
        autoscroll = checked;
    }

    // Error Highlights Rendering
    function renderErrorHighlights(errors) {
        if (!Array.isArray(errors) || errors.length === 0) {
            $("#errorHighlightWrapper").html(
                `<p><i class="ui green circle check icon"></i> No error found</p>`
            );
            return;
        }
        let html = `<div class="ui list">`;
        errors.forEach(function(err) {
            let [timestamp, method, path, code] = err;
            html += `
                <div class="item">
                    <div class="ui ${code.startsWith('5') ? 'red' : 'yellow'} message" style="margin-bottom:0.7em;">
                        <div class="header">
                            <span style="font-family:monospace;">[${timestamp}]</span>
                            <span style="font-family:monospace;">${method}</span>
                            <span style="margin-left:1em;font-family:monospace;">${path}</span>
                            <span style="float:right;font-weight:bold;">${code}</span>
                        </div>
                    </div>
                </div>
            `;
        });
        html += `</div>`;
        $("#errorHighlightWrapper").html(html);
        //Scroll to bottom of the error highlight
        $("#errorHighlightWrapper").scrollTop($("#errorHighlightWrapper")[0].scrollHeight);
    }
</script>
</html>